﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.ServiceModel.Channels;
using System.Collections.Concurrent;
using MCB.Net.Utility;
using System.Collections.Generic;
using MCB.Net.Protocol;

namespace MCB.Net.Server
{ 
    public class TcpServer
    {
        protected int _maximumConnections;  
        protected int _clientBufferSize; 
        
        protected Socket listenSocket;

        protected Pool<SocketAsyncEventArgs> _acceptPool = new Pool<SocketAsyncEventArgs>();
        protected Pool<SocketAsyncEventArgs> _readWritePool = new Pool<SocketAsyncEventArgs>();
         
        protected Semaphore _maximumAcceptedClientsSemaphore;

        protected int _maximumAcceptedConnections = 1;

        public TcpConnectionManager Connections { get; set; }

        protected ProtocolProcessor _factory = new ProtocolProcessor();

        public TcpServer(int numConnections, int receiveBufferSize)
        {
            Connections = new TcpConnectionManager();

            _maximumConnections = numConnections;
            _clientBufferSize = receiveBufferSize;

            _maximumAcceptedConnections = numConnections;

            _maximumAcceptedClientsSemaphore = new Semaphore(_maximumAcceptedConnections, _maximumAcceptedConnections);

            Initialize();
        }

        protected void Initialize()
        {
            //Create accept pool
            for (int index = 0; index < _maximumAcceptedConnections; index++)
            {
                _acceptPool.Add(Create(0));
            }

            for (int i = 0; i < _maximumConnections; i++)
            {
                _readWritePool.Add(Create(25));
            }
        }

        protected SocketAsyncEventArgs Create(int buffer, Pool<SocketAsyncEventArgs> pool = null)
        {
            SocketAsyncEventArgs readWriteEventArg = null;
            bool addProperties = false;

            if (pool == null)
            {
                readWriteEventArg = new SocketAsyncEventArgs();
                addProperties = true;
            }
            else
            {
                readWriteEventArg = pool.Remove();
                addProperties = true;
            }

            if (pool!=null && addProperties)
            {
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.UserToken = new TcpConnection();
                if (buffer > 0)
                    readWriteEventArg.SetBuffer(new byte[buffer], 0, buffer);
            }
            return readWriteEventArg;
        }


        public void Start(IPEndPoint localEndPoint)
        {
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            listenSocket.Listen(100);

            StartAccept();
        }
 
        protected void StartAccept()
        {
            SocketAsyncEventArgs acceptEventArg = Create(0, _acceptPool);
            acceptEventArg.Completed += AcceptEventArg_Completed;

            _maximumAcceptedClientsSemaphore.WaitOne();
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)
            {
                Socket_Accept(acceptEventArg);
            }
        }

        protected void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket_Accept(e);
        }

        protected void Socket_Accept(SocketAsyncEventArgs e)
        {
            SocketAsyncEventArgs readEventArgs = Create(25,_readWritePool);
            ((TcpConnection)readEventArgs.UserToken).Socket = e.AcceptSocket;
            readEventArgs.AcceptSocket = e.AcceptSocket;
            readEventArgs.Completed += Socket_OperationCompleted;
            Start_Receive(readEventArgs);

            //Return to the pool
            _acceptPool.Add(e);

            StartAccept();
        }

        protected void Start_Receive(SocketAsyncEventArgs readEventArgs)
        {
            bool willRaiseEvent = readEventArgs.AcceptSocket.ReceiveAsync(readEventArgs);
            if (!willRaiseEvent)
            {
                Socket_Receive(readEventArgs);
            }
        }

        protected void Socket_OperationCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    Socket_Receive(e);
                    break;
                case SocketAsyncOperation.Send:
                    Socket_Send(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }

        }

        protected void Socket_Receive(SocketAsyncEventArgs e)
        {
            ASCIIEncoding encoding = new ASCIIEncoding();
            TcpConnection token = (TcpConnection)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                if (token.Protocol == null)
                {
                    Array.Copy(e.Buffer, 0, token.InitBuffer, token.InitPosition, e.BytesTransferred);
                    token.InitPosition += e.BytesTransferred;

                    IProtocol protocol = _factory.GetProtocol(token.InitBuffer);

                    if (protocol != null)
                        token.Protocol = protocol;
                }

                if (token.Protocol != null)
                {
                    token.Protocol.Read(e.Buffer);
                }

                e.AcceptSocket = token.Socket;
                Start_Receive(e);
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        protected void Socket_Send(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                // done echoing data back to the client
                TcpConnection token = (TcpConnection)e.UserToken;
                // read the next block of data send from the client 
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    Socket_Receive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        protected void CloseClientSocket(SocketAsyncEventArgs e)
        {
            TcpConnection token = e.UserToken as TcpConnection;

            // close the socket associated with the client 
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            finally
            {
                token.Socket.Close();
            }

            _maximumAcceptedClientsSemaphore.Release();

            // Free the SocketAsyncEventArg so they can be reused by another client
            _readWritePool.Add(e);
        }

    }    
}